﻿using System;
using System.Collections.Generic;
using System.Linq;

using VSTalk.Engine.Core.Context;
using VSTalk.Engine.Core.EntitySession;
using VSTalk.Engine.Core.Event.Events;
using VSTalk.Engine.Core.Feed;
using VSTalk.Model;
using VSTalk.Tools;

namespace VSTalk.Engine.Core.XmppConnection
{
    public class ConnectionManager : IConnectionManager, IListener, IDisposable
    {
        private readonly IList<ClientConnection> _registeredConnections = new List<ClientConnection>();
        
        private ISubscription _clientUnit;
        private IDisposable _connectEvent;
        private IDisposable _initEvent;
        private IDisposable _disconnectEvent;

        public static IModelContext ModelContext
        {
            get { return AppServices.Get<IModelContext>(); }
        }

        public static IMessageFeed Feed
        {
            get { return AppServices.Get<IMessageFeed>(); }
        }

        private static ISessionModel SessionModel
        {
            get { return AppServices.Get<ISessionModel>(); }
        }

        private static IEventAggregator EventBus
        {
            get { return AppServices.Get<IEventAggregator>(); }
        }

        public ConnectionManager()
        {
            StartListen();
        }

        public void StartListen()
        {
            _clientUnit = ModelContext.Clients.HandleRemoveChanges(removed: client =>
            {
                var target = _registeredConnections.FirstOrDefault(connection => connection.Client == client);
                if (target == null) return;
                target.StopListen();
                Feed.UnregisterProvider(target.Provider);
                _registeredConnections.Remove(target);
            });

            _connectEvent = EventBus.GetEvent<ConnectClientEvent>().Subscribe(@event =>
            {
                var client = ModelContext.GetClientById(@event.ClientId);
                Connect(client);
            });

            _disconnectEvent = EventBus.GetEvent<DisconnectClientEvent>().Subscribe(@event =>
            {
                var client = ModelContext.GetClientById(@event.ClientId);
                Disconnect(client);
            });

            _initEvent = EventBus.GetEvent<AppInitializedEvent>().Subscribe(@event => ConnectClients());
        }

        public void StopListen()
        {
            _clientUnit.Unsubscribe();
            _connectEvent.Dispose();
            _disconnectEvent.Dispose();
            _initEvent.Dispose();
        }

        public ClientConnection GetOrCreateConnection(Client client)
        {
            return _registeredConnections.FirstOrDefault(connection => connection.Client == client)
                   ?? CreateConnection(client);
        }

        private ClientConnection CreateConnection(Client client)
        {
            var connection = new ClientConnection(client);
            _registeredConnections.Add(connection);
            Feed.RegisterProvider(connection.Provider);
            connection.StartListen();
            return connection;
        }

        public void Connect(Client client)
        {
            var context = SessionModel.GetClientContext(client);
            if (context.State != ClientState.Disconnected)
            {
                return;
            }
            var connection = GetOrCreateConnection(client);
            connection.Connect();
        }

        public void Disconnect(Client client)
        {
            var context = SessionModel.GetClientContext(client);
            if (context.State != ClientState.Connected)
            {
                return;
            }
            var targetConnection = _registeredConnections.First(connection => connection.Client == client);
            targetConnection.Disconnect();
        }

        public void ConnectClients()
        {
            foreach (var client in ModelContext.Clients)
            {
                Connect(client);
            }
        }

        public void DisconnectClients()
        {
            foreach (var client in ModelContext.Clients)
            {
                Disconnect(client);
            }
        }

        public void Dispose()
        {
            DisconnectClients();
            StopListen();
        }
    }
}